/*
 * Copyright (C) 2006-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

package org.alfresco.jlan.server.auth.passthru;

import java.io.IOException;

import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.netbios.NetworkSession;
import org.alfresco.jlan.netbios.RFCNetBIOSProtocol;
import org.alfresco.jlan.server.auth.PasswordEncryptor;
import org.alfresco.jlan.server.auth.ntlm.NTLM;
import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage;
import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage;
import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage;
import org.alfresco.jlan.smb.Capability;
import org.alfresco.jlan.smb.Dialect;
import org.alfresco.jlan.smb.NTTime;
import org.alfresco.jlan.smb.PCShare;
import org.alfresco.jlan.smb.PacketType;
import org.alfresco.jlan.smb.SMBDate;
import org.alfresco.jlan.smb.SMBException;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.jlan.util.DataPacker;

/**
 * Authenticate Session Class
 *
 * <p>Used for passthru authentication mechanisms.
 *
 * @author gkspencer
 */
public class AuthenticateSession {

  //	Default packet size

  private static final int DefaultPacketSize	=	1024;

	//	Session security mode

	public static final int SecurityModeUser	= 1;
	public static final int SecurityModeShare	= 2;

	//	Tree identifier that indicates that the disk session has been closed

  protected final static int Closed = -1;

	//	Debug flags

  public final static int DBGPacketType 	= 0x0001;
  public final static int DBGDumpPacket 	= 0x0002;
  public final static int DBGHexDump 			= 0x0004;

	//	Default SMB packet size to allocate

  public static final int DEFAULT_BUFSIZE = 4096;

	//	SMB dialect id and string for this session

  private int m_dialect;
  private String m_diaStr;

	//	Network session

  private NetworkSession m_netSession;

	//	SMB packet for protocol exhanges

  protected SMBPacket m_pkt;

	//	Default packet flags

	private int m_defFlags 	= SMBPacket.FLG_CASELESS;
	private int m_defFlags2 = SMBPacket.FLG2_LONGFILENAMES;

	//	Server connection details

  private PCShare m_remoteShr;

	//	Domain name

  private String m_domain;

	//	Remote operating system and LAN manager type

  private String m_srvOS;
  private String m_srvLM;

	//	Security mode (user or share)

	private int m_secMode;

	//	Challenge encryption key

  private byte[] m_encryptKey;

	//	SMB session information

  private int m_sessIdx;
  private int m_userId;
  private int m_processId;

	//	Tree identifier for this connection

  protected int m_treeid;

	//	Device type that this session is connected to

  private int m_devtype;

	//	Maximum transmit buffer size allowed

  private int m_maxPktSize;

	//	Session capabilities

  private int m_sessCaps;

	//	Maximum virtual circuits allowed on this session, and maximum multiplxed read/writes

	private int m_maxVCs;
	private int m_maxMPX;

	//	Indicate if the session was created as a guest rather than using the supplied username/password

	private boolean m_guest;

  // Flag to indicate extended security exchange is being used

  private boolean m_extendedSec;

  // Server GUID, if using extended security

  private byte[] m_serverGUID;

  // Type 2 security blob from the server

  private Type2NTLMMessage m_type2Msg;

	//	Global session id

  private static int m_sessionIdx = 1;

	//	Multiplex id

	private static int m_multiplexId = 1;

	//	Debug support

  private static int m_debug = 0;

	/**
	 * Class constructor
	 *
	 * @param shr PCShare
	 * @param sess NetworkSession
	 * @param dialect int
	 * @param pkt SMBPacket
	 * @exception IOException
	 * @exception SMBException
	 */
	protected AuthenticateSession(PCShare shr, NetworkSession sess, int dialect, SMBPacket pkt)
	  throws IOException, SMBException {

    //  Set the SMB dialect for this session

    m_dialect = dialect;

    //	Save the remote share details

    m_remoteShr = shr;

    //	Allocate a unique session index

    m_sessIdx = getNextSessionId();

    //  Allocate an SMB protocol packet

		m_pkt = pkt;
		if ( pkt == null)
    	m_pkt = new SMBPacket(DEFAULT_BUFSIZE);

		//	Save the session and packet

		setSession( sess);

		//	Extract the details from the negotiate response packet

		processNegotiateResponse();
	}

  /**
   * Allocate an SMB packet for this session. The preferred packet size is
   * specified, if a smaller buffer size has been negotiated a smaller SMB
   * packet will be returned.
   *
   * @param pref   Preferred SMB packet size
   * @return       Allocated SMB packet
   */
  protected final SMBPacket allocatePacket(int pref) {

    //  Check if the preferred size is larger than the maximum allowed packet
    //  size for this session.

    if (pref > m_maxPktSize)
      return new SMBPacket(m_maxPktSize + RFCNetBIOSProtocol.HEADER_LEN);

    //  Return the preferred SMB packet size

    return new SMBPacket(pref + RFCNetBIOSProtocol.HEADER_LEN);
  }

  /**
   * Determine if the session supports extended security
   *
   * @return true if this session supports extended security, else false
   */
  public final boolean supportsExtendedSecurity() {
    return (m_sessCaps & Capability.ExtendedSecurity) != 0 ? true : false;
  }

  /**
   * Determine if the session supports raw mode read/writes
   *
   * @return   true if this session supports raw mode, else false
   */
  public final boolean supportsRawMode() {
    return (m_sessCaps & Capability.RawMode) != 0 ? true : false;
  }

	/**
	 * Determine if the session supports Unicode
	 *
	 * @return boolean
	 */
	public final boolean supportsUnicode() {
    return (m_sessCaps & Capability.Unicode) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports large files (ie. 64 bit file offsets)
	 *
	 * @return boolean
	 */
	public final boolean supportsLargeFiles() {
    return (m_sessCaps & Capability.LargeFiles) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports NT specific SMBs
	 *
	 * @return boolean
	 */
	public final boolean supportsNTSmbs() {
    return (m_sessCaps & Capability.NTSMBs) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports RPC API requests
	 *
	 * @return boolean
	 */
	public final boolean supportsRPCAPIs() {
    return (m_sessCaps & Capability.RemoteAPIs) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports NT status codes
	 *
	 * @return boolean
	 */
	public final boolean supportsNTStatusCodes() {
    return (m_sessCaps & Capability.NTStatus) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports level 2 oplocks
	 *
	 * @return boolean
	 */
	public final boolean supportsLevel2Oplocks() {
    return (m_sessCaps & Capability.Level2Oplocks) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports lock and read
	 *
	 * @return boolean
	 */
	public final boolean supportsLockAndRead() {
    return (m_sessCaps & Capability.LockAndRead) != 0 ? true : false;
	}

	/**
	 * Determine if the session supports NT find
	 *
	 * @return boolean
	 */
	public final boolean supportsNTFind() {
    return (m_sessCaps & Capability.NTFind) != 0 ? true : false;
	}

  /**
   * Close this connection with the remote server.
   *
   * @exception  java.io.IOException   If an I/O error occurs.
   * @exception	 SMBException					 If an SMB level error occurs
   */
  public void CloseSession()
   	throws java.io.IOException, SMBException {

    //  If the session is valid then hangup the session

    if (isActive()) {

      //  Close the network session

      m_netSession.Close();

      //  Clear the session

      m_netSession = null;
    }
  }

	/**
	 * Return the default flags settings for this session
	 *
	 * @return int
	 */
	public final int getDefaultFlags() {
		return m_defFlags;
	}

	/**
	 * Return the default flags2 settings for this session
	 *
	 * @return int
	 */
	public final int getDefaultFlags2() {
		return m_defFlags2;
	}

  /**
   * Get the device type that this session is connected to.
   *
   * @return     Device type for this session.
   */
  public final int getDeviceType() {
    return m_devtype;
  }

  /**
   * Get the SMB dialect property
   *
   * @return       SMB dialect that this session has negotiated.
   */
  public final int getDialect() {
    return m_dialect;
  }

  /**
   * Get the SMB dialect string
   *
   * @return       SMB dialect string for this session.
   */
  public final String getDialectString() {
    return m_diaStr;
  }

  /**
   * Get the servers primary domain name
   *
   * @return     Servers primary domain name, if knwon, else null.
   */
  public final String getDomain() {
    return m_domain;
  }

	/**
	 * Determine if there is a challenge encryption key
	 *
	 * @return boolean
	 */
	public final boolean hasEncryptionKey() {
		return m_encryptKey != null ? true : false;
	}

  /**
   * Return the cahllenge encryption key
   *
   * @return byte[]
   */
  public final byte[] getEncryptionKey() {
    return m_encryptKey;
  }

  /**
   * Get the servers LAN manager type
   *
   * @return     Servers LAN manager type, if known, else null.
   */
  public final String getLANManagerType() {
    return m_srvLM;
  }

	/**
	 * Get the maximum number of multiplxed requests that are allowed
	 *
	 * @return int
	 */
	public final int getMaximumMultiplexedRequests() {
		return m_maxMPX;
	}

  /**
   * Get the maximum packet size allowed for this session
   *
   * @return     Maximum packet size, in bytes.
   */
  public final int getMaximumPacketSize() {
    return m_maxPktSize;
  }

  /**
   * Get the maximum virtual circuits allowed on this session
   *
   * @return int
   */
  public final int getMaximumVirtualCircuits() {
  	return m_maxVCs;
  }

  /**
   * Get the next multiplex id to uniquely identify a transaction
   *
   * @return    Unique multiplex id for a transaction
   */
  public final synchronized int getNextMultiplexId() {
    return m_multiplexId++;
  }

	/**
	 * Get the next session id
	 *
	 * @return int
	 */
	protected final synchronized int getNextSessionId() {
		return m_sessionIdx++;
	}

  /**
   * Get the servers operating system type
   *
   * @return     Servers operating system, if known, else null.
   */
  public final String getOperatingSystem() {
    return m_srvOS;
  }

  /**
   * Get the remote share password string
   *
   * @return   Remote share password string
   */
  public final String getPassword() {
    return m_remoteShr.getPassword();
  }

  /**
   * Get the remote share details for this session
   *
   * @return   PCShare information for this session
   */
  public final PCShare getPCShare() {
    return m_remoteShr;
  }

  /**
   * Return the security mode of the session (user or share)
   *
   * @return int
   */
  public final int getSecurityMode() {
  	return m_secMode;
  }

  /**
   * Get the remote server name
   *
   * @return   Remote server name
   */
  public final String getServer() {
    return m_remoteShr.getNodeName();
  }

  /**
   * Access the associated network session
   *
   * @return   NetworkSession that the SMB session is using
   */
  public final NetworkSession getSession() {
    return m_netSession;
  }

  /**
   * Return the session capability flags.
   *
   * @return int
   */
  public final int getCapabilities() {
    return m_sessCaps;
  }

	/**
	 * Get the process id for this session
	 *
	 * @return int
	 */
	public final int getProcessId() {
		return m_processId;
	}

  /**
   * Get the session identifier property
   *
   * @return   Session identifier
   */
  public final int getSessionId() {
    return m_sessIdx;
  }

  /**
   * Get the remote share name
   *
   * @return   Remote share name string
   */
  public final String getShareName() {
    return m_remoteShr.getShareName();
  }

  /**
   * Get the connected tree identifier.
   *
   * @return   Tree identifier.
   */
  public final int getTreeId() {
    return m_treeid;
  }

  /**
   * Return the assigned use id for this SMB session
   *
   * @return     Assigned user id
   */
  public final int getUserId() {
    return m_userId;
  }

  /**
   * Get the remote share user name string
   *
   * @return   Remote share user name string
   */
  public final String getUserName() {
    return m_remoteShr.getUserName();
  }

  /**
   * Check if there is data available in the network receive buffer
   *
   * @return boolean
   * @exception IOException
   */
  public final boolean hasDataAvailable()
  	throws IOException {
    return m_netSession.hasData();
  }

  /**
   * Determine if the specified debugging option is enabled
   *
   * @param opt    Debug option bit mask
   * @return       true if the debug option is enabled, else false
   */
  public static boolean hasDebugOption(int opt) {
    if (m_debug == 0)
      return false;
    if ((m_debug & opt) != 0)
      return true;
    return false;
  }

  /**
   * Determine if the session is valid, ie. still open.
   *
   * @return   true if the session is still active, else false.
   */
  public final boolean isActive() {
    return (m_netSession == null) ? false : true;
  }

  /**
   * Determine if SMB session debugging is enabled
   *
   * @return   true if debugging is enabled, else false.
   */
  public static boolean hasDebug() {
    return m_debug != 0 ? true : false;
  }

  /**
   * Determine if the session has been created as a guest logon
   *
   * @return boolean
   */
  public final boolean isGuest() {
  	return m_guest;
  }

  /**
   *	Determine if the Unicode flag is enabled
   *
   * @return boolean
   */
  public final boolean isUnicode() {
  	return (m_defFlags2 & SMBPacket.FLG2_UNICODE) != 0 ? true : false;
  }

  /**
   * Determine if extended security exchanges are being used
   *
   * @return boolean
   */
  public final boolean isUsingExtendedSecurity() {
    return m_extendedSec;
  }

  /**
   * Send a single echo request to the server
   *
   * @throws java.io.IOException
   * @throws SMBException
   */
  public final void pingServer()
		throws java.io.IOException, SMBException {

		//	Send a single echo request to the server

		pingServer(1);
	}

  /**
   * Send an echo request to the server
   *
   * @param cnt    Number of packets to echo from the remote server
   * @exception    java.io.IOException           If an I/O error occurs
   * @exception    SMBException   SMB error occurred
   */
  public final void pingServer(int cnt)
    throws java.io.IOException, SMBException {

    //  Build a server ping SMB packet

    m_pkt.setCommand(PacketType.Echo);
    m_pkt.setFlags(0);
    m_pkt.setTreeId(getTreeId());
    m_pkt.setUserId(getUserId());
    m_pkt.setProcessId(getProcessId());
    m_pkt.setMultiplexId(1);

    //  Set the parameter words

    m_pkt.setParameterCount(1);
    m_pkt.setParameter(0, cnt); //  number of packets that the server should return
    String echoStr = "ECHO";
    m_pkt.setBytes(echoStr.getBytes());

    //  Send the echo request

    m_pkt.SendSMB(this);

    //  Receive the reply packets, if any

    while (cnt > 0) {

      //  Receive a reply packet

      m_pkt.ReceiveSMB(this);

      //  Decrement the reply counter

      cnt--;
    }
  }

  /**
   * Enable/disable SMB session debugging
   *
   * @param dbg    Bit mask of debug options to enable, or zero to disable
   */
  public static void setDebug(int dbg) {
    m_debug = dbg;
  }

  /**
   * Set the default SMB packet flags for this session
   *
   * @param flg int
   */
  protected final void setDefaultFlags(int flg) {
  	m_defFlags = flg;
  }

  /**
   * Set the SMB packet default flags2 for this session
   *
   * @param flg2 int
   */
  protected final void setDefaultFlags2(int flg2) {
  	m_defFlags2 = flg2;
  }

  /**
   * Set the device type for this session.
   *
   * @param dev    Device type for this session.
   */
  protected final void setDeviceType(int dev) {
    m_devtype = dev;
  }

  /**
   * Set the dialect for this session
   *
   * @param dia    SMB dialect that this session is using.
   */
  protected final void setDialect(int dia) {
    m_dialect = dia;
  }

  /**
   * Set the dialect string for this session
   *
   * @param dia    SMB dialect string
   */
  protected final void setDialectString(String dia) {
    m_diaStr = dia;
  }

  /**
   * Set the remote servers primary domain name
   *
   * @param dom    Servers primary domain name.
   */
  protected final void setDomain(String dom) {
    m_domain = dom;
  }

  /**
   * Set the encryption key
   *
   * @param key byte[]
   */
  public final void setEncryptionKey(byte[] key) {

    //	Set the challenge response encryption key

    m_encryptKey = key;
  }

	/**
	 * Set the guest status for the session
	 *
	 * @param sts boolean
	 */
	protected final void setGuest(boolean sts) {
		m_guest = sts;
	}

  /**
   * Set the remote servers LAN manager type
   *
   * @param lm     Servers LAN manager type string.
   */
  protected final void setLANManagerType(String lm) {
    m_srvLM = lm;
  }

  /**
   * Set the maximum number of multiplexed requests allowed
   *
   * @param maxMulti int
   */
  protected final void setMaximumMultiplexedRequests(int maxMulti) {
  	m_maxMPX = maxMulti;
  }

  /**
   * Set the maximum packet size allowed on this session
   *
   * @param siz    Maximum allowed packet size.
   */
  protected final void setMaximumPacketSize(int siz) {
    m_maxPktSize = siz;
  }

	/**
	 * Set the maximum number of virtual circuits allowed on this session
	 *
	 * @param maxVC int
	 */
	protected final void setMaximumVirtualCircuits(int maxVC) {
		m_maxVCs = maxVC;
	}

  /**
   * Set the remote servers operating system type
   *
   * @param os     Servers operating system type string.
   */
  protected final void setOperatingSystem(String os) {
    m_srvOS = os;
  }

  /**
   * Set the remote share password
   *
   * @param pwd    Remtoe share password string.
   */
  protected final void setPassword(String pwd) {
    m_remoteShr.setPassword(pwd);
  }

  /**
   * Set the session security mode (user or share)
   *
   * @param secMode int
   */
  public final void setSecurityMode(int secMode) {
  	m_secMode = secMode;
  }

  /**
   * Set the remote server name
   *
   * @param srv    Server name string
   */
  protected final void setServer(String srv) {
    m_remoteShr.setNodeName(srv);
  }

  /**
   * Set the network session that this SMB session is associated with
   *
   * @param netSess  Network session that this SMB session is to be associated
   *                 with.
   */
  protected final void setSession(NetworkSession netSess) {
    m_netSession = netSess;
  }

  /**
   * Set the session capability flags
   *
   * @param caps    Capability flags.
   */
  protected final void setCapabilities(int caps) {
  	m_sessCaps = caps;
  }

  /**
   * Set the remote share name
   *
   * @param shr    Remote share name string
   */
  protected final void setShareName(String shr) {
    m_remoteShr.setShareName(shr);
  }

	/**
	 * Set the process id for this session
	 *
	 * @param id int
	 */
	protected final void setProcessId(int id) {
		m_processId = id;
	}

  /**
   * Set the connected tree identifier for this session.
   *
   * @param id Tree identifier for this session.
   */
  protected final void setTreeId(int id) {
    m_treeid = id;
  }

  /**
   * Set the user identifier for this session
   *
   * @param uid    User identifier
   */
  protected final void setUserId(int uid) {
    m_userId = uid;
  }

  /**
   * Set the remote share user name
   *
   * @param user   Remote share user name string
   */
  protected final void setUserName(String user) {
    m_remoteShr.setUserName(user);
  }

	/**
	 * Process an asynchronous packet
	 *
	 * @param pkt SMBPacket
	 */
	protected void processAsynchResponse(SMBPacket pkt) {

		//	Default is to ignore the packet
		//
		//	This method is overridden by SMB dialects that can generate asynchronous responses

		if ( Debug.EnableInfo && hasDebug())
			Debug.println("++ Asynchronous response received, command = 0x" + pkt.getCommand());
	}

  /**
   * Output the session details as a string
   *
   * @return   Session details string
   */
  public String toString() {
  	StringBuffer str = new StringBuffer();

  	str.append("[\\\\");
  	str.append(getServer());
  	str.append("\\");
  	str.append(getShareName());
  	str.append(":");
  	str.append(Dialect.DialectTypeString(m_dialect));
  	str.append(",UserId=");
  	str.append(getUserId());
  	str.append("]");

  	return str.toString();
  }

  /**
   * Perform a session setup to create a session on the remote server validating the user.
   *
   * @param userName String
   * @param ascPwd ASCII password hash
   * @param uniPwd Unicode password hash
   * @exception IOException If a network error occurs
   * @exception SMBException If a CIFS error occurs
   */
  public final void doSessionSetup(String userName, byte[] ascPwd, byte[] uniPwd) throws IOException, SMBException
  {
      doSessionSetup(null, userName, null, ascPwd, uniPwd, 0);
  }

  /**
   * Perform a session using the type3 NTLM response received from the client
   *
   * @param type3Msg Type3NTLMMessage
   * @exception IOException If a network error occurs
   * @exception SMBException If a CIFS error occurs
   */
  public final void doSessionSetup(Type3NTLMMessage type3Msg) throws IOException, SMBException
  {
      doSessionSetup(type3Msg.getDomain(), type3Msg.getUserName(), type3Msg.getWorkstation(),
              type3Msg.getLMHash(), type3Msg.getNTLMHash(), 0);
  }

  /**
   * Perform a session setup to create a session on the remote server validating the user.
   *
   * @param domain String
   * @param userName String
   * @param wksName String
   * @param ascPwd ASCII password hash
   * @param uniPwd Unicode password hash
   * @param vcNum Virtual circuit number
   * @exception IOException If a network error occurs
   * @exception SMBException If a CIFS error occurs
   */
  public void doSessionSetup(String domain, String userName, String wksName,
          byte[] ascPwd, byte[] uniPwd, int vcNum)
    throws IOException, SMBException
  {
      // Check if we are using extended security

      if ( isUsingExtendedSecurity())
      {
          // Run the second phase of the extended security session setup

          doExtendedSessionSetupPhase2(domain, userName, wksName, ascPwd, uniPwd, vcNum);
          return;
      }

      // Create a session setup packet

      SMBPacket pkt = new SMBPacket();

      pkt.setCommand(PacketType.SessionSetupAndX);

      // Check if the negotiated SMB dialect is NT LM 1.2 or an earlier dialect

      if (getDialect() == Dialect.NT)
      {

          // NT LM 1.2 SMB dialect

          int defFlags2 = SMBPacket.FLG2_UNICODE;
          pkt.setFlags2(defFlags2);

          pkt.setParameterCount(13);
          pkt.setAndXCommand(0xFF); // no secondary command
          pkt.setParameter(1, 0); // offset to next command
          pkt.setParameter(2, DefaultPacketSize);
          pkt.setParameter(3, 1);
          pkt.setParameter(4, vcNum);
          pkt.setParameterLong(5, 0); // session key

          // Set the share password length(s)

          pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0); // ANSI password length
          pkt.setParameter(8, uniPwd != null ? uniPwd.length : 0); // Unicode password length

          pkt.setParameter(9, 0); // reserved, must be zero
          pkt.setParameter(10, 0); // reserved, must be zero

          // Send the client capabilities

          int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus
                  + Capability.RemoteAPIs;

          // Set the client capabilities

          pkt.setParameterLong(11, caps);

          // Get the offset to the session setup request byte data

          int pos = pkt.getByteOffset();
          pkt.setPosition(pos);

          // Store the ASCII password hash, if specified

          if (ascPwd != null)
              pkt.packBytes(ascPwd, ascPwd.length);

          // Store the Unicode password hash, if specified

          if (uniPwd != null)
              pkt.packBytes(uniPwd, uniPwd.length);

          // Pack the account/client details

          pkt.packString(userName, true);

          // Check if the share has a domain, if not then use the default domain string

          if (getPCShare().hasDomain())
              pkt.packString(getPCShare().getDomain(), true);
          else if ( domain != null)
            pkt.packString( domain, true);
          else
              pkt.packString("?", true);

          pkt.packString("Java VM", true);
          pkt.packString("Alfresco CIFS", true);

          // Set the packet length

          pkt.setByteCount(pkt.getPosition() - pos);
      }
      else
      {

          // Earlier SMB dialect

          pkt.setUserId(1);

          pkt.setParameterCount(10);
          pkt.setAndXCommand(0xFF);
          pkt.setParameter(1, 0);
          pkt.setParameter(2, DefaultPacketSize);
          pkt.setParameter(3, 1);
          pkt.setParameter(4, 0);
          pkt.setParameter(5, 0);
          pkt.setParameter(6, 0);
          pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0);
          pkt.setParameter(8, 0);
          pkt.setParameter(9, 0);

          // Put the password into the SMB packet

          byte[] buf = pkt.getBuffer();
          int pos = pkt.getByteOffset();

          if (ascPwd != null)
          {
              for (int i = 0; i < ascPwd.length; i++)
                  buf[pos++] = ascPwd[i];
          }

          // Build the account/client details

          StringBuffer clbuf = new StringBuffer();

          clbuf.append(getPCShare().getUserName());
          clbuf.append((char) 0x00);

          // Check if the share has a domain, if not then use the unknown domain string

          if (getPCShare().hasDomain())
              clbuf.append(getPCShare().getDomain());
          else
              clbuf.append("?");
          clbuf.append((char) 0x00);

          clbuf.append("Java VM");
          clbuf.append((char) 0x00);

          clbuf.append("Alfresco CIFS");
          clbuf.append((char) 0x00);

          // Copy the remaining data to the SMB packet

          byte[] byts = clbuf.toString().getBytes();
          for (int i = 0; i < byts.length; i++)
              buf[pos++] = byts[i];

          int pwdLen = ascPwd != null ? ascPwd.length : 0;
          pkt.setByteCount(pwdLen + byts.length);
      }

      // Exchange an SMB session setup packet with the remote file server

      pkt.ExchangeSMB(this, pkt, true);

      // Save the session user id

      setUserId(pkt.getUserId());

      // Check if the session was created as a guest

      if (pkt.getParameterCount() >= 3)
      {

          // Set the guest status for the session

          setGuest(pkt.getParameter(2) != 0 ? true : false);
      }

      // The response packet should also have the server OS, LAN Manager type
      // and primary domain name.

      if (pkt.getByteCount() > 0)
      {

          // Get the packet buffer and byte offset

          byte[] buf = pkt.getBuffer();
          int offset = pkt.getByteOffset();
          int maxlen = offset + pkt.getByteCount();

          // Get the server OS

          String srvOS = DataPacker.getString(buf, offset, maxlen);
          setOperatingSystem(srvOS);

          offset += srvOS.length() + 1;
          maxlen -= srvOS.length() + 1;

          // Get the LAN Manager type

          String lanman = DataPacker.getString(buf, offset, maxlen);
          setLANManagerType(lanman);

          // Check if we have the primary domain for this session

          if (getDomain() == null || getDomain().length() == 0)
          {

              // Get the domain name string

              offset += lanman.length() + 1;
              maxlen += lanman.length() + 1;

              String dom = DataPacker.getString(buf, offset, maxlen);
              setDomain(dom);
          }
      }

      // Check for a core protocol session, set the maximum packet size

      if (getDialect() == Dialect.Core || getDialect() == Dialect.CorePlus)
      {

          // Set the maximum packet size to be used on this session

          setMaximumPacketSize(pkt.getParameter(2));
      }
  }

  /**
   * Process the negotiate response SMB packet
   *
   * @exception IOException If a network error occurs
   * @exception SMBException If a CIFS error occurs
   */
  private void processNegotiateResponse() throws IOException, SMBException
  {

      // Set the security mode flags

      int keyLen = 0;
      boolean unicodeStr = false;
      int encAlgorithm = PasswordEncryptor.LANMAN;
      int defFlags2 = 0;

      if (getDialect() == Dialect.NT)
      {
          // Read the returned negotiate parameters, for NT dialect the parameters are not aligned

          m_pkt.resetParameterPointer();
          m_pkt.skipBytes(2); // skip the dialect index

          setSecurityMode(m_pkt.unpackByte());

          // Set the maximum virtual circuits and multiplxed requests allowed by the server

          setMaximumMultiplexedRequests(m_pkt.unpackWord());
          setMaximumVirtualCircuits(m_pkt.unpackWord());

          // Set the maximum buffer size

          setMaximumPacketSize(m_pkt.unpackInt());

          // Skip the maximum raw buffer size and session key

          m_pkt.skipBytes(8);

          // Set the server capabailities

          setCapabilities(m_pkt.unpackInt());

          // Check if extended security is enabled

          if ( supportsExtendedSecurity())
              m_extendedSec = true;

          // Get the server system time and timezone

          SMBDate srvTime = NTTime.toSMBDate(m_pkt.unpackLong());
          int tzone = m_pkt.unpackWord();

          // Get the encryption key length

          keyLen = m_pkt.unpackByte();

          // Indicate that strings are UniCode

          unicodeStr = true;

          // Use NTLMv1 password encryption

          encAlgorithm = PasswordEncryptor.NTLM1;

          // Set the default flags for subsequent SMB requests

          defFlags2 = SMBPacket.FLG2_LONGFILENAMES + SMBPacket.FLG2_UNICODE + SMBPacket.FLG2_LONGERRORCODE + SMBPacket.FLG2_SECURITYSIG;

          if ( isUsingExtendedSecurity())
              defFlags2 += SMBPacket.FLG2_EXTENDEDSECURITY;
      }
      else if (getDialect() > Dialect.CorePlus)
      {

          // Set the security mode and encrypted password mode

          int secMode = m_pkt.getParameter(1);
          setSecurityMode((secMode & 0x01) != 0 ? SecurityModeUser : SecurityModeShare);

          if (m_pkt.getParameterCount() >= 11)
              keyLen = m_pkt.getParameter(11) & 0xFF; // should always be 8

          // Set the maximum virtual circuits and multiplxed requests allowed by the server

          setMaximumMultiplexedRequests(m_pkt.getParameter(3));
          setMaximumVirtualCircuits(m_pkt.getParameter(4));

          // Check if Unicode strings are being used

          if (m_pkt.isUnicode())
              unicodeStr = true;

          // Set the default flags for subsequent SMB requests

          defFlags2 = SMBPacket.FLG2_LONGFILENAMES;
      }

      // Set the default packet flags for this session

      setDefaultFlags2(defFlags2);

      // Get the server details from the negotiate SMB packet

      if (m_pkt.getByteCount() > 0)
      {

          // Get the returned byte area length and offset

          int bytsiz = m_pkt.getByteCount();
          int bytpos = m_pkt.getByteOffset();
          byte[] buf = m_pkt.getBuffer();

          // Original format response

          if ( isUsingExtendedSecurity() == false)
          {
              // Extract the challenge response key, if specified

              if (keyLen > 0)
              {

                  // Allocate a buffer for the challenge response key

                  byte[] encryptKey = new byte[keyLen];

                  // Copy the challenge response key

                  for (int keyIdx = 0; keyIdx < keyLen; keyIdx++)
                      encryptKey[keyIdx] = buf[bytpos++];

                  // Set the sessions encryption key

                  setEncryptionKey(encryptKey);
              }

              // Extract the domain name

              String dom;

              if (unicodeStr == false)
                  dom = DataPacker.getString(buf, bytpos, bytsiz);
              else
                  dom = DataPacker.getUnicodeString(buf, bytpos, bytsiz / 2);
              setDomain(dom);
          }
          else
          {
              // Extract the server GUID

              m_serverGUID = new byte[16];
              System.arraycopy(buf, bytpos, m_serverGUID, 0, 16);

              // Run the first phase of the extended security session setup to get the challenge
              // from the server

              doExtendedSessionSetupPhase1();
          }
      }
  }

  /**
   * Send the first stage of the extended security session setup
   *
   * @exception IOException If a network error occurs
   * @exception SMBException If a CIFS error occurs
   */
  private final void doExtendedSessionSetupPhase1() throws IOException, SMBException
  {
      // Create a session setup packet

      SMBPacket pkt = new SMBPacket();

      pkt.setCommand(PacketType.SessionSetupAndX);

      pkt.setFlags(getDefaultFlags());
      pkt.setFlags2(getDefaultFlags2());

      // Build the extended session setup phase 1 request

      pkt.setParameterCount(12);
      pkt.setAndXCommand(0xFF);   // no secondary command
      pkt.setParameter(1, 0);     // offset to next command
      pkt.setParameter(2, DefaultPacketSize);
      pkt.setParameter(3, 1);
      pkt.setParameter(4, 0);     // virtual circuit number
      pkt.setParameterLong(5, 0); // session key

      // Clear the security blob length and reserved area

      pkt.setParameter(7, 0);     // security blob length
      pkt.setParameterLong(8, 0); // reserved

      // Send the client capabilities

      int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus
              + Capability.RemoteAPIs + Capability.ExtendedSecurity;

      // Set the client capabilities

      pkt.setParameterLong(10, caps);

      // Get the offset to the session setup request byte data

      int pos = pkt.getByteOffset();
      pkt.setPosition(pos);

      // Create a type 1 NTLM message using the session setup request buffer

      Type1NTLMMessage type1Msg = new Type1NTLMMessage(pkt.getBuffer(), pos, 0);

      int type1Flags = getPCShare().getExtendedSecurityFlags();
      if ( type1Flags == 0)
          type1Flags = NTLM.FlagNegotiateUnicode + NTLM.FlagNegotiateNTLM + NTLM.FlagRequestTarget;

      type1Msg.buildType1(type1Flags, null, null);

      // Update the request buffer position

      pkt.setPosition(pos + type1Msg.getLength());

      // Set the security blob length

      pkt.setParameter(7, type1Msg.getLength());

      // Pack the OS details

      pkt.packString("Java VM", true);
      pkt.packString("Alfresco CIFS", true);

      pkt.packString("", true);

      // Set the packet length

      pkt.setByteCount(pkt.getPosition() - pos);

      // Exchange an SMB session setup packet with the remote file server

      pkt.ExchangeSMB(this, pkt, false);

      // Check the error status, should be a warning status to indicate more processing required

      if ( pkt.isLongErrorCode() == false || pkt.getLongErrorCode() != SMBStatus.NTMoreProcessingRequired)
          pkt.checkForError();

      // Save the session user id

      setUserId(pkt.getUserId());

      // The response packet should also have the type 2 security blob

      int type2Len = pkt.getParameter(3);
      if (pkt.getByteCount() > 0)
      {

          // Get the packet buffer and byte offset

          byte[] buf = pkt.getBuffer();
          int offset = pkt.getByteOffset();
          int maxlen = offset + pkt.getByteCount();

          // Take a copy of the type 2 security blob

          m_type2Msg = new Type2NTLMMessage();
          m_type2Msg.copyFrom(buf, offset, type2Len);

          // Get the encryption key from the security blob

          m_encryptKey = m_type2Msg.getChallenge();

          // Update the byte area offset and align

          offset = DataPacker.wordAlign(offset + type2Len);
          maxlen -= type2Len;

          // Get the server OS

          String srvOS = DataPacker.getString(buf, offset, maxlen);
          setOperatingSystem(srvOS);

          offset += srvOS.length() + 1;
          maxlen -= srvOS.length() + 1;

          // Get the LAN Manager type

          String lanman = DataPacker.getString(buf, offset, maxlen);
          setLANManagerType(lanman);
      }
  }

 /**
  * Send the second stage of the extended security session setup
  *
  * @param domain String
  * @param userName String
  * @param wksName String
  * @param lmPwd byte[]
  * @param ntlmPwd byte[]
  * @param vcNum int
  * @exception IOException If a network error occurs
  * @exception SMBException If a CIFS error occurs
  */
 private final void doExtendedSessionSetupPhase2(String domain, String userName, String wksName,
         byte[] lmPwd, byte[] ntlmPwd, int vcNum) throws IOException, SMBException
 {
     // Check if the domain name has been specified, if not then use the domain name from the
     // original connection details or the servers domain name

     if ( domain == null)
     {
         if ( getPCShare().hasDomain() && getPCShare().getDomain().length() > 0)
             domain = getPCShare().getDomain();
         else
             domain = m_type2Msg.getTarget();
     }

     // Create a session setup packet

     SMBPacket pkt = new SMBPacket();

     pkt.setCommand(PacketType.SessionSetupAndX);

     pkt.setFlags(getDefaultFlags());
     pkt.setFlags2(getDefaultFlags2());

     pkt.setUserId(getUserId());

     // Build the extended session setup phase 2 request

     pkt.setParameterCount(12);
     pkt.setAndXCommand(0xFF);   // no secondary command
     pkt.setParameter(1, 0);     // offset to next command
     pkt.setParameter(2, DefaultPacketSize);
     pkt.setParameter(3, 1);
     pkt.setParameter(4, vcNum);
     pkt.setParameterLong(5, 0); // session key

     // Clear the security blob length and reserved area

     pkt.setParameter(7, 0);     // security blob length
     pkt.setParameterLong(8, 0); // reserved

     // Send the client capabilities

     int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus
             + Capability.RemoteAPIs + Capability.ExtendedSecurity;

     // Set the client capabilities

     pkt.setParameterLong(10, caps);

     // Get the offset to the session setup request byte data

     int pos = pkt.getByteOffset();
     pkt.setPosition(pos);

     // Create a type 3 NTLM message using the session setup request buffer

     Type3NTLMMessage type3Msg = new Type3NTLMMessage(pkt.getBuffer(), pos, 0, true);

     type3Msg.buildType3(lmPwd, ntlmPwd, domain, userName, wksName != null ? wksName : "", null, m_type2Msg.getFlags());

     // Update the request buffer position

     pkt.setPosition(pos + type3Msg.getLength());

     // Set the security blob length

     pkt.setParameter(7, type3Msg.getLength());

     // Pack the OS details

     pkt.packString("Java VM", true);
     pkt.packString("Alfresco CIFS", true);

     pkt.packString("", true);

     // Set the packet length

     pkt.setByteCount(pkt.getPosition() - pos);

     // Exchange an SMB session setup packet with the remote file server

     pkt.ExchangeSMB(this, pkt, true);

     // Set the guest status for the session

     setGuest(pkt.getParameter(2) != 0 ? true : false);
  }
}
